import { fetchResource } from "../utils/fetchResource.js";
import { sandBox } from "../sandbox/index.js";
// import { performScriptForEval } from "../sandbox/performScript.js";

// 加载 html 的方法
export const loadHtml = async (app) => {
  // 第一个，子应用需要显示在哪里 subNavList container
  let container = app.container; // #id 内容，获取页面上对应的容器
  // 子应用的入口 subNavList entry
  let entry = app.entry;
  // 我们的子应用要显示在当前的 container 容器里
  // 那么接下来呢我们就通过其它的方法来将我们的子应用加载进来，那么这里呢我们就不在当前的 loadHtml 方法里面写了
  const [dom, scripts] = await parseHtml(entry, app.name);

  // console.log(scripts);
  const ct = document.querySelector(container);
  if (!ct) {
    throw new Error("容器不存在，请查看");
  }
  ct.innerHTML = dom;
  // 那么对于我们所有 js 文件的执行我们需要将它放在我们的 dom 执行完成之后，那么这样呢才不会有一些获取不到的错误
  scripts.forEach((item) => {
    // performScriptForEval(item);
    sandBox(app, item);
  });
  return app;
};

// 提高加载性能 - 应用缓存
// 如果我们当前这个html也就解析并且加载过那么这时候呢我们就直接返回我们已经加载过的内容，但是呢如果没有加载过的话我们继续让它走之前我们需要走的加载我们子应用的所有的流程
const cache = {}; // 根据子应用的 name 来做缓存

export const parseHtml = async (entry, name) => {
  if(cache[name]){
    return cache[name]; // 如果说当前命中了我们的 name，我们就直接使用 name 缓存的当前的子应用的所有的信息
  }
  // 如果没有命中缓存的内容这里呢我们就需要让它继续走我们之前所有的加载的逻辑
  // axios、ajax、fetch
  const html = await fetchResource(entry); // fetch 其实也是一个 promise 的请求

  let allScript = [];
  const div = document.createElement("div");
  div.innerHTML = html;
  // 标签、link、script(src，js)
  const [dom, scriptUrl, script] = await getResources(div, entry);

  // 这样我们就可以获取所有的js的资源
  const fetchedScripts = await Promise.all(
    scriptUrl.map(async (item) => fetchResource(item))
  );
  allScript = script.concat(fetchedScripts); // 构成了我们子应用所有的 script 的集合

  // 但是呢我们需要在这里给它做一步对应的缓存
  cache[name] = [dom, allScript];

  return [dom, allScript]; // 来将我们的子应用渲染到页面上，我们在其它的方法里将这个 allScript 运行起来可以得到我们完整的子应用的渲染
};

export const getResources = async (root, entry) => {
  const scriptUrl = [];
  const script = [];
  const dom = root.outerHTML;

  // 深度解析
  // 我们需要处理的就是当前以 root 为根路径下的所有的子路径，但凡是有 script 或是 link 我们都给它单独的提炼出来
  function deepParse(element) {
    const children = element.children;
    const parent = element.parent;

    // 第一步处理位于 script 中的内容，如何判断我们当前这个标签是不是 script 标签呢
    if (element.nodeName.toLowerCase() === "script") {
      const src = element.getAttribute("src"); // 得到当前 element 的 src 参数
      if (!src) {
        // 说明我们是直接在 script 中书写的内容并没有外链其它的js资源，那么这个时候呢我们就需要获取到当前 script 标签中的所有内容将它添加到上面的 script 这个数组中
        script.push(element.outerHTML);
      } else {
        // src 有内容这个时候表明当前的js资源是通过外部链接引入的，那么这个时候呢我们就要通过它这个链接去获取到它对应的内容
        if (src.startsWith("http")) {
          scriptUrl.push(src);
        } else {
          scriptUrl.push(`http:${entry}/${src}`); // 如果js资源文件不是以http开头的
        }
      }
      if (parent) {
        parent.replaceChild(
          document.createComment("此js文件已经被微前端替换", element)
        );
      }
    }
    // link 也会有js的内容
    if (element.nodeName.toLowerCase() === "link") {
      const href = element.getAttribute("href");
      if (href.endsWith(".js")) {
        if (href.startsWith("http")) {
          scriptUrl.push(href);
        } else {
          scriptUrl.push(`http:${entry}/${href}`); // 如果js资源文件不是以http开头的
        }
      }
    }
    for (let i = 0; i < children.length; i++) {
      deepParse(children[i]);
    }
  }
  deepParse(root);
  return [dom, scriptUrl, script];
};
